// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================

package org.eclipse.jetty.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/**
 * 
 *  
 */
public abstract class AbstractBuffer implements Buffer
{

	private static final Logger LOG = Log.getLogger(AbstractBuffer.class);

	@SuppressWarnings("unused")
	private final static boolean __boundsChecking = Boolean.getBoolean("org.eclipse.jetty.io.AbstractBuffer.boundsChecking");

	protected final static String
		__IMMUTABLE = "IMMUTABLE",
		__READONLY = "READONLY",
		__READWRITE = "READWRITE",
		__VOLATILE = "VOLATILE";

	protected int _access;
	protected boolean _volatile;

	protected int _get;
	protected int _put;
	protected int _hash;
	protected int _hashGet;
	protected int _hashPut;
	protected int _mark;
	protected String _string;
	protected View _view;

	/**
	 * Constructor for BufferView
	 * 
	 * @param access 0==IMMUTABLE, 1==READONLY, 2==READWRITE
	 */
	public AbstractBuffer(int access, boolean isVolatile)
	{
		if (access == IMMUTABLE && isVolatile)
			throw new IllegalArgumentException("IMMUTABLE && VOLATILE");
		setMarkIndex(-1);
		_access = access;
		_volatile = isVolatile;
	}

	/*
	 * @see org.eclipse.io.Buffer#toArray()
	 */
	public byte[] asArray()
	{
		byte[] bytes = new byte[length()];
		byte[] array = array();
		if (array != null)
			System.arraycopy(array, getIndex(), bytes, 0, bytes.length);
		else
			peek(getIndex(), bytes, 0, length());
		return bytes;
	}

	public ByteArrayBuffer duplicate(int access)
	{
		Buffer b = this.buffer();
		if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve)
			return new ByteArrayBuffer.CaseInsensitive(asArray(), 0, length(), access);
		else
			return new ByteArrayBuffer(asArray(), 0, length(), access);
	}

	/*
	 * @see org.eclipse.io.Buffer#asNonVolatile()
	 */
	public Buffer asNonVolatileBuffer()
	{
		if (!isVolatile())
			return this;
		return duplicate(_access);
	}

	public Buffer asImmutableBuffer()
	{
		if (isImmutable())
			return this;
		return duplicate(IMMUTABLE);
	}

	/*
	 * @see org.eclipse.util.Buffer#asReadOnlyBuffer()
	 */
	public Buffer asReadOnlyBuffer()
	{
		if (isReadOnly())
			return this;
		return new View(this, markIndex(), getIndex(), putIndex(), READONLY);
	}

	public Buffer asMutableBuffer()
	{
		if (!isImmutable())
			return this;

		Buffer b = this.buffer();
		if (b.isReadOnly())
		{
			return duplicate(READWRITE);
		}
		return new View(b, markIndex(), getIndex(), putIndex(), _access);
	}

	public Buffer buffer()
	{
		return this;
	}

	public void clear()
	{
		setMarkIndex(-1);
		setGetIndex(0);
		setPutIndex(0);
	}

	public void compact()
	{
		if (isReadOnly())
			throw new IllegalStateException(__READONLY);
		int s = markIndex() >= 0 ? markIndex() : getIndex();
		if (s > 0)
		{
			byte array[] = array();
			int length = putIndex() - s;
			if (length > 0)
			{
				if (array != null)
					System.arraycopy(array(), s, array(), 0, length);
				else
					poke(0, peek(s, length));
			}
			if (markIndex() > 0)
				setMarkIndex(markIndex() - s);
			setGetIndex(getIndex() - s);
			setPutIndex(putIndex() - s);
		}
	}

	@Override
	public boolean equals(Object obj)
	{
		if (obj == this)
			return true;

		// reject non buffers;
		if (obj == null || !(obj instanceof Buffer))
			return false;
		Buffer b = (Buffer)obj;

		if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve)
			return equalsIgnoreCase(b);

		// reject different lengths
		if (b.length() != length())
			return false;

		// reject AbstractBuffer with different hash value
		if (_hash != 0 && obj instanceof AbstractBuffer)
		{
			AbstractBuffer ab = (AbstractBuffer)obj;
			if (ab._hash != 0 && _hash != ab._hash)
				return false;
		}

		// Nothing for it but to do the hard grind.
		int get = getIndex();
		int bi = b.putIndex();
		for (int i = putIndex(); i-- > get;)
		{
			byte b1 = peek(i);
			byte b2 = b.peek(--bi);
			if (b1 != b2)
				return false;
		}
		return true;
	}

	public boolean equalsIgnoreCase(Buffer b)
	{
		if (b == this)
			return true;

		// reject different lengths
		if (b.length() != length())
			return false;

		// reject AbstractBuffer with different hash value
		if (_hash != 0 && b instanceof AbstractBuffer)
		{
			AbstractBuffer ab = (AbstractBuffer)b;
			if (ab._hash != 0 && _hash != ab._hash)
				return false;
		}

		// Nothing for it but to do the hard grind.
		int get = getIndex();
		int bi = b.putIndex();

		byte[] array = array();
		byte[] barray = b.array();
		if (array != null && barray != null)
		{
			for (int i = putIndex(); i-- > get;)
			{
				byte b1 = array[i];
				byte b2 = barray[--bi];
				if (b1 != b2)
				{
					if ('a' <= b1 && b1 <= 'z')
						b1 = (byte)(b1 - 'a' + 'A');
					if ('a' <= b2 && b2 <= 'z')
						b2 = (byte)(b2 - 'a' + 'A');
					if (b1 != b2)
						return false;
				}
			}
		}
		else
		{
			for (int i = putIndex(); i-- > get;)
			{
				byte b1 = peek(i);
				byte b2 = b.peek(--bi);
				if (b1 != b2)
				{
					if ('a' <= b1 && b1 <= 'z')
						b1 = (byte)(b1 - 'a' + 'A');
					if ('a' <= b2 && b2 <= 'z')
						b2 = (byte)(b2 - 'a' + 'A');
					if (b1 != b2)
						return false;
				}
			}
		}
		return true;
	}

	public byte get()
	{
		return peek(_get++);
	}

	public int get(byte[] b, int offset, int length)
	{
		int gi = getIndex();
		int l = length();
		if (l == 0)
			return -1;

		if (length > l)
			length = l;

		length = peek(gi, b, offset, length);
		if (length > 0)
			setGetIndex(gi + length);
		return length;
	}

	public Buffer get(int length)
	{
		int gi = getIndex();
		Buffer view = peek(gi, length);
		setGetIndex(gi + length);
		return view;
	}

	public final int getIndex()
	{
		return _get;
	}

	public boolean hasContent()
	{
		return _put > _get;
	}

	@Override
	public int hashCode()
	{
		if (_hash == 0 || _hashGet != _get || _hashPut != _put)
		{
			int get = getIndex();
			byte[] array = array();
			if (array == null)
			{
				for (int i = putIndex(); i-- > get;)
				{
					byte b = peek(i);
					if ('a' <= b && b <= 'z')
						b = (byte)(b - 'a' + 'A');
					_hash = 31 * _hash + b;
				}
			}
			else
			{
				for (int i = putIndex(); i-- > get;)
				{
					byte b = array[i];
					if ('a' <= b && b <= 'z')
						b = (byte)(b - 'a' + 'A');
					_hash = 31 * _hash + b;
				}
			}
			if (_hash == 0)
				_hash = -1;
			_hashGet = _get;
			_hashPut = _put;

		}
		return _hash;
	}

	public boolean isImmutable()
	{
		return _access <= IMMUTABLE;
	}

	public boolean isReadOnly()
	{
		return _access <= READONLY;
	}

	public boolean isVolatile()
	{
		return _volatile;
	}

	public int length()
	{
		return _put - _get;
	}

	public void mark()
	{
		setMarkIndex(_get - 1);
	}

	public void mark(int offset)
	{
		setMarkIndex(_get + offset);
	}

	public int markIndex()
	{
		return _mark;
	}

	public byte peek()
	{
		return peek(_get);
	}

	public Buffer peek(int index, int length)
	{
		if (_view == null)
		{
			_view = new View(this, -1, index, index + length, isReadOnly() ? READONLY : READWRITE);
		}
		else
		{
			_view.update(this.buffer());
			_view.setMarkIndex(-1);
			_view.setGetIndex(0);
			_view.setPutIndex(index + length);
			_view.setGetIndex(index);

		}
		return _view;
	}

	public int poke(int index, Buffer src)
	{
		_hash = 0;
		/* 
		if (isReadOnly()) 
		    throw new IllegalStateException(__READONLY);
		if (index < 0) 
		    throw new IllegalArgumentException("index<0: " + index + "<0");
		*/

		int length = src.length();
		if (index + length > capacity())
		{
			length = capacity() - index;
			/*
			if (length<0)
			    throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
			*/
		}

		byte[] src_array = src.array();
		byte[] dst_array = array();
		if (src_array != null && dst_array != null)
			System.arraycopy(src_array, src.getIndex(), dst_array, index, length);
		else if (src_array != null)
		{
			int s = src.getIndex();
			for (int i = 0; i < length; i++)
				poke(index++, src_array[s++]);
		}
		else if (dst_array != null)
		{
			int s = src.getIndex();
			for (int i = 0; i < length; i++)
				dst_array[index++] = src.peek(s++);
		}
		else
		{
			int s = src.getIndex();
			for (int i = 0; i < length; i++)
				poke(index++, src.peek(s++));
		}

		return length;
	}

	public int poke(int index, byte[] b, int offset, int length)
	{
		_hash = 0;
		/*
		if (isReadOnly()) 
		    throw new IllegalStateException(__READONLY);
		if (index < 0) 
		    throw new IllegalArgumentException("index<0: " + index + "<0");
		*/
		if (index + length > capacity())
		{
			length = capacity() - index;
			/* if (length<0)
			    throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
			*/
		}

		byte[] dst_array = array();
		if (dst_array != null)
			System.arraycopy(b, offset, dst_array, index, length);
		else
		{
			int s = offset;
			for (int i = 0; i < length; i++)
				poke(index++, b[s++]);
		}
		return length;
	}

	public int put(Buffer src)
	{
		int pi = putIndex();
		int l = poke(pi, src);
		setPutIndex(pi + l);
		return l;
	}

	public void put(byte b)
	{
		int pi = putIndex();
		poke(pi, b);
		setPutIndex(pi + 1);
	}

	public int put(byte[] b, int offset, int length)
	{
		int pi = putIndex();
		int l = poke(pi, b, offset, length);
		setPutIndex(pi + l);
		return l;
	}

	public int put(byte[] b)
	{
		int pi = putIndex();
		int l = poke(pi, b, 0, b.length);
		setPutIndex(pi + l);
		return l;
	}

	public final int putIndex()
	{
		return _put;
	}

	public void reset()
	{
		if (markIndex() >= 0)
			setGetIndex(markIndex());
	}

	public void rewind()
	{
		setGetIndex(0);
		setMarkIndex(-1);
	}

	public void setGetIndex(int getIndex)
	{
		/* bounds checking
		if (isImmutable()) 
		    throw new IllegalStateException(__IMMUTABLE);
		if (getIndex < 0)
		    throw new IllegalArgumentException("getIndex<0: " + getIndex + "<0");
		if (getIndex > putIndex())
		    throw new IllegalArgumentException("getIndex>putIndex: " + getIndex + ">" + putIndex());
		 */
		_get = getIndex;
		_hash = 0;
	}

	public void setMarkIndex(int index)
	{
		/*
		if (index>=0 && isImmutable()) 
		    throw new IllegalStateException(__IMMUTABLE);
		*/
		_mark = index;
	}

	public void setPutIndex(int putIndex)
	{
		/* bounds checking
		if (isImmutable()) 
		    throw new IllegalStateException(__IMMUTABLE);
		if (putIndex > capacity())
		        throw new IllegalArgumentException("putIndex>capacity: " + putIndex + ">" + capacity());
		if (getIndex() > putIndex)
		        throw new IllegalArgumentException("getIndex>putIndex: " + getIndex() + ">" + putIndex);
		 */
		_put = putIndex;
		_hash = 0;
	}

	public int skip(int n)
	{
		if (length() < n)
			n = length();
		setGetIndex(getIndex() + n);
		return n;
	}

	public Buffer slice()
	{
		return peek(getIndex(), length());
	}

	public Buffer sliceFromMark()
	{
		return sliceFromMark(getIndex() - markIndex() - 1);
	}

	public Buffer sliceFromMark(int length)
	{
		if (markIndex() < 0)
			return null;
		Buffer view = peek(markIndex(), length);
		setMarkIndex(-1);
		return view;
	}

	public int space()
	{
		return capacity() - _put;
	}

	public String toDetailString()
	{
		StringBuilder buf = new StringBuilder();
		buf.append("[");
		buf.append(super.hashCode());
		buf.append(",");
		buf.append(this.buffer().hashCode());
		buf.append(",m=");
		buf.append(markIndex());
		buf.append(",g=");
		buf.append(getIndex());
		buf.append(",p=");
		buf.append(putIndex());
		buf.append(",c=");
		buf.append(capacity());
		buf.append("]={");
		if (markIndex() >= 0)
		{
			for (int i = markIndex(); i < getIndex(); i++)
			{
				byte b = peek(i);
				TypeUtil.toHex(b, buf);
			}
			buf.append("}{");
		}
		int count = 0;
		for (int i = getIndex(); i < putIndex(); i++)
		{
			byte b = peek(i);
			TypeUtil.toHex(b, buf);
			if (count++ == 50)
			{
				if (putIndex() - i > 20)
				{
					buf.append(" ... ");
					i = putIndex() - 20;
				}
			}
		}
		buf.append('}');
		return buf.toString();
	}

	/* ------------------------------------------------------------ */
	@Override
	public String toString()
	{
		if (isImmutable())
		{
			if (_string == null)
				_string = new String(asArray(), 0, length());
			return _string;
		}
		return new String(asArray(), 0, length());
	}

	/* ------------------------------------------------------------ */
	public String toString(String charset)
	{
		try
		{
			byte[] bytes = array();
			if (bytes != null)
				return new String(bytes, getIndex(), length(), charset);
			return new String(asArray(), 0, length(), charset);

		} catch (Exception e)
		{
			LOG.warn(e);
			return new String(asArray(), 0, length());
		}
	}

	/* ------------------------------------------------------------ */
	public String toDebugString()
	{
		return getClass() + "@" + super.hashCode();
	}

	/* ------------------------------------------------------------ */
	public void writeTo(OutputStream out)
		throws IOException
	{
		byte[] array = array();

		if (array != null)
		{
			out.write(array, getIndex(), length());
		}
		else
		{
			int len = this.length();
			byte[] buf = new byte[len > 1024 ? 1024 : len];
			int offset = _get;
			while (len > 0)
			{
				int l = peek(offset, buf, 0, len > buf.length ? buf.length : len);
				out.write(buf, 0, l);
				offset += l;
				len -= l;
			}
		}
		clear();
	}

	/* ------------------------------------------------------------ */
	public int readFrom(InputStream in, int max) throws IOException
	{
		byte[] array = array();
		int s = space();
		if (s > max)
			s = max;

		if (array != null)
		{
			int l = in.read(array, _put, s);
			if (l > 0)
				_put += l;
			return l;
		}
		else
		{
			byte[] buf = new byte[s > 1024 ? 1024 : s];
			int total = 0;
			while (s > 0)
			{
				int l = in.read(buf, 0, buf.length);
				if (l < 0)
					return total > 0 ? total : -1;
				int p = put(buf, 0, l);
				assert l == p;
				s -= l;
			}
			return total;
		}
	}
}
